// Copyright (C) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.Management.Automation;
using System.Management.Automation.Host;
using System.Management.Automation.Runspaces;
using System.Globalization;
using System.Collections.ObjectModel;
using System.Threading;

namespace Microsoft.VSPowerToys.VSCmdShell.Hosts
{
    /// <summary>
    /// UI layer implementation for PowerShell host. This layer handles basic read/write operations.
    /// </summary>
    class PowerShellHostUI : PSHostUserInterface
    {

        #region Private Variables

        private IShellUI shellUI;

        private PowerShellHostRawUI myUI;

        
        #endregion

        #region Constructors
        public PowerShellHostUI(IShellUI shellUI)
            : base()
        {
            this.shellUI = shellUI;
            this.myUI = new PowerShellHostRawUI(this.shellUI);
        }

        #endregion

        #region PSHostUserInterface methods

        public override Dictionary<string, PSObject> Prompt(string caption, string message, System.Collections.ObjectModel.Collection<FieldDescription> descriptions)
        {
            this.WriteLine(caption + Environment.NewLine + message);
            Dictionary<string, PSObject> results = new Dictionary<string, PSObject>();
            foreach (FieldDescription fd in descriptions)
            {
                this.Write(fd.Label.Replace("&", String.Empty) + ": ");
                string userData = this.ReadLine();
                if (userData == null)
                    return null;
                results[fd.Name] = PSObject.AsPSObject(userData);
            }
            return results;

        }

        public override int PromptForChoice(string caption, string message, System.Collections.ObjectModel.Collection<ChoiceDescription> choices, int defaultChoice)
        {
            WriteLine(PSHostStrings.PromptForChoice + message);
            for (int i = 0; i < choices.Count; i++)
            {
                string label = choices[i].Label;
                label = label.Replace("&", String.Empty);
                this.WriteLine((i + 1) + "> " + choices[i].Label);
            }
            while (true)
            {
                string s = this.ReadLine();
                int choice = 0;
                if (int.TryParse(s, NumberStyles.Integer, System.Globalization.CultureInfo.CurrentCulture, out choice) &&
                    choice > 0 && choice <= choices.Count)
                {
                    return choice - 1;
                }
                else
                {
                    this.WriteErrorLine(PSHostStrings.MessageInvalidChoice);
                    WriteLine(PSHostStrings.PromptForChoice + message);
                }
            }
        }

        public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName)
        {
            throw new NotImplementedException();
        }

        public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options)
        {
            throw new NotImplementedException();
        }

        public override PSHostRawUserInterface RawUI
        {
            get { return this.myUI; }
        }
        
        public override string ReadLine()
        {
            return this.shellUI.ReadLine();
        }

        public override System.Security.SecureString ReadLineAsSecureString()
        {
            throw new NotImplementedException();
        }

        public override void Write(string value)
        {
            this.shellUI.Write(value);
        }

        public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)
        {
            this.shellUI.Write(value);
        }

        public override void WriteDebugLine(string message)
        {
            this.shellUI.WriteLine(String.Format(System.Globalization.CultureInfo.CurrentCulture, PSHostStrings.FomartDebug, message));
        }

        public override void WriteErrorLine(string value)
        {
            this.shellUI.WriteLine(String.Format(System.Globalization.CultureInfo.CurrentCulture, PSHostStrings.FormatError, value));
        }

        public override void WriteLine()
        {
            this.shellUI.WriteLine(String.Empty);
        }

        public override void WriteLine(string value)
        {
            this.shellUI.WriteLine(value);
        }

        public override void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)
        {
            this.shellUI.WriteLine(value);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sourceId"></param>
        /// <param name="record"></param>
        /// <remarks>Not supported by current implementation</remarks>
        public override void WriteProgress(long sourceId, ProgressRecord record)
        {
            return;
        }

        public override void WriteVerboseLine(string message)
        {
            this.shellUI.WriteLine(String.Format(System.Globalization.CultureInfo.CurrentCulture, PSHostStrings.FormatVerbose, message));
        }

        public override void WriteWarningLine(string message)
        {
            this.shellUI.WriteLine(String.Format(System.Globalization.CultureInfo.CurrentCulture, PSHostStrings.FormatWarning, message));
        }

        #endregion
    }

    /// <summary>
    /// Raw UI layer implementation for PowerShell host. Most of the interface methods are not supported because we
    /// are not using a real console window.
    /// 
    /// This layer handles more complex IO operations such as screen buffering.
    /// </summary>
    public class PowerShellHostRawUI : PSHostRawUserInterface, IDisposable
    {

        #region Private Variables

        private IShellUI shellUI;

        private Collection<KeyInfo> inputKeyBuffer = new Collection<KeyInfo>();

        private ManualResetEvent KeyRead = new ManualResetEvent(false);

        #endregion
        
        #region Constructors

        public PowerShellHostRawUI(IShellUI shellUI) : base()
        {
            this.shellUI = shellUI;
            this.shellUI.KeyDown += new System.Windows.Forms.KeyEventHandler(shellUI_KeyDown);
            this.shellUI.KeyUp += new System.Windows.Forms.KeyEventHandler(shellUI_KeyUp);
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Retrieves key events from shell UI and puts them in to key buffer
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void shellUI_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
        {
            KeyInfo info = ConvertKeyEventsArgsToKeyInfo(e);
            info.KeyDown = true;
            lock (this.inputKeyBuffer)
            {
                this.inputKeyBuffer.Add(info);
            }
            this.KeyRead.Set();
            
        }

        /// <summary>
        /// Retrieves key events from shell UI and puts them in to key buffer
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void shellUI_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
        {
            KeyInfo info = ConvertKeyEventsArgsToKeyInfo(e);
            info.KeyDown = false;
            lock (this.inputKeyBuffer)
            {
                this.inputKeyBuffer.Add(info);
            }
            this.KeyRead.Set();
            
        }

        /// <summary>
        /// Converts a KeyEventArgs object from Windows forms key events to KeyInfo object to be used
        /// by PowerShell.
        /// </summary>
        /// <param name="keyEvent"></param>
        /// <returns></returns>
        private static KeyInfo ConvertKeyEventsArgsToKeyInfo(System.Windows.Forms.KeyEventArgs keyEvent)
        {
            KeyInfo info = new KeyInfo();
            info.VirtualKeyCode = (int)keyEvent.KeyCode;
            info.Character = (char)(keyEvent.KeyData);
            if ((keyEvent.Modifiers & System.Windows.Forms.Keys.LControlKey) != 0)
            {
                info.ControlKeyState |= ControlKeyStates.LeftCtrlPressed;
            }
            if ((keyEvent.Modifiers & System.Windows.Forms.Keys.RControlKey) != 0)
            {
                info.ControlKeyState |= ControlKeyStates.RightCtrlPressed;
            }
            if ((keyEvent.Modifiers & System.Windows.Forms.Keys.Alt) != 0)
            {
                info.ControlKeyState |= ControlKeyStates.LeftAltPressed | ControlKeyStates.RightAltPressed;
            }
            if ((keyEvent.Modifiers & System.Windows.Forms.Keys.CapsLock) != 0)
            {
                info.ControlKeyState |= ControlKeyStates.CapsLockOn;
            }
            if ((keyEvent.Modifiers & System.Windows.Forms.Keys.NumLock) != 0)
            {
                info.ControlKeyState |= ControlKeyStates.NumLockOn;
            }
            if ((keyEvent.Modifiers & System.Windows.Forms.Keys.Shift) != 0)
            {
                info.ControlKeyState |= ControlKeyStates.ShiftPressed;
            }
            return info;
        }
        #endregion

        #region PSHostRawUserInterface methods
        public override void FlushInputBuffer()
        {
            lock (this.inputKeyBuffer)
            {
                this.inputKeyBuffer.Clear();
            }
            
        }

        /// <summary>
        /// Non blocking method to check if a key is available in the buffer.
        /// </summary>
        public override bool KeyAvailable
        {
            get {
                bool retValue = false;
                lock (this.inputKeyBuffer)
                {
                    retValue = this.inputKeyBuffer.Count > 0;
                }
                return retValue;
            }
        }

        /// <summary>
        /// Blocking method to retrieve the next key event available in the buffer. If there is no
        /// key event caller will be blocked.
        /// </summary>
        /// <param name="options"></param>
        /// <returns></returns>
        public override KeyInfo ReadKey(ReadKeyOptions options)
        {
            while (true)
            {
                this.KeyRead.WaitOne();
                lock (this.inputKeyBuffer)
                {
                    while (this.inputKeyBuffer.Count > 0)
                    {
                        KeyInfo info = this.inputKeyBuffer[0];
                        this.inputKeyBuffer.RemoveAt(0);
                        if (
                            ((options & ReadKeyOptions.IncludeKeyDown) != 0 && info.KeyDown) ||
                            ((options & ReadKeyOptions.IncludeKeyUp) != 0 && !info.KeyDown))
                        {
                            this.KeyRead.Reset();
                            return info;
                        }
                    }
                }
                this.KeyRead.Reset();
            }
        }

        public override Coordinates CursorPosition
        {
            get { throw new NotImplementedException(); }
            set { throw new NotImplementedException(); }
        }


        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1814:PreferJaggedArraysOverMultidimensional", MessageId = "Return")]
        public override BufferCell[,] GetBufferContents(Rectangle rectangle)
        {
            throw new NotImplementedException();
        }


        public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill)
        {
            throw new NotImplementedException();
        }


        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1814:PreferJaggedArraysOverMultidimensional", MessageId = "1#")]
        public override void SetBufferContents(Coordinates origin, BufferCell[,] contents)
        {
            throw new NotImplementedException();
        }

        public override void SetBufferContents(Rectangle rectangle, BufferCell fill)
        {
            throw new NotImplementedException();
        }

        public override ConsoleColor BackgroundColor
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public override Size BufferSize
        {
            get
            {
                return this.WindowSize;
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        /// <summary>
        /// Gets the cursor size. In our case, cursor size is always 0 and we don't have the
        /// ability to set it.
        /// </summary>
        public override int CursorSize
        {
            get
            {
                return 0;
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public override ConsoleColor ForegroundColor
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public override Size MaxPhysicalWindowSize
        {
            get { throw new NotImplementedException(); }
        }

        public override Size MaxWindowSize
        {
            get { throw new NotImplementedException(); }
        }

        public override Coordinates WindowPosition
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public override Size WindowSize
        {
            get
            {
                return new Size(this.shellUI.WindowWidth, this.shellUI.WindowHeight);
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public override string WindowTitle
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            this.Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            this.KeyRead.Close();
        }
        #endregion
    }
}
